package com.biz.primus.ms.auth.service;

import com.biz.primus.common.utils.Timers;
import com.biz.primus.base.enums.CommonStatus;
import com.biz.primus.model.sso.auth.vo.*;
import com.biz.primus.ms.base.service.AbstractBaseService;
import java.util.*;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import com.biz.primus.model.sso.auth.config.ServiceAuthorityConfig;
import com.biz.primus.ms.auth.dao.po.*;
import com.biz.primus.ms.auth.dao.repository.*;
import com.biz.primus.ms.auth.dao.vo.ParsedAuthorityVO;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

/**
 * 服务模块 Service
 * Created by defei on 8/1/17.
 */
@Service
public class AuthorityService extends AbstractBaseService {

    private static final Logger logger = LoggerFactory.getLogger(AuthorityService.class);

    private final ServiceAuthorityConfig serviceAuthorityConfig;

    private UserRepository userRepository;

    private RoleRepository roleRepository;

    private MenuGroupRepository menuGroupRepository;

    private MenuItemRepository menuItemRepository;

    private ResourceRepository resourceRepository;

    private final ApplicationTypeRepository applicationTypeRepository;

    @Autowired
    public AuthorityService(UserRepository userRepository, RoleRepository roleRepository,
                            MenuGroupRepository menuGroupRepository,
                            MenuItemRepository menuItemRepository,
                            ResourceRepository resourceRepository,
                            ServiceAuthorityConfig serviceAuthorityConfig, ApplicationTypeRepository applicationTypeRepository) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.menuGroupRepository = menuGroupRepository;
        this.menuItemRepository = menuItemRepository;
        this.resourceRepository = resourceRepository;
        this.serviceAuthorityConfig = serviceAuthorityConfig;
        this.applicationTypeRepository = applicationTypeRepository;
    }

    @PostConstruct
    public void setup() {

    }

    /**
     * 服务模块上报菜单及权限
     */
    @Transactional
    public void authorityRegister(MenusRegisterReqVO reqVO) {
        logger.debug("Update menus:{}", reqVO);
        ParsedAuthorityVO parsedAuthorityVO = convertToAuthorityEntities(reqVO.getApplicationType(), reqVO.getMenuGroups(), null);
        Map<String, MenuGroup> menuGroupCustomerIdToMenuGroup = updateMenuGroup(parsedAuthorityVO);
        Map<String, MenuItem> menuItemCustomerIdToMenuItem = updateMenuItems(menuGroupCustomerIdToMenuGroup, parsedAuthorityVO);
        updateResources(menuItemCustomerIdToMenuItem, parsedAuthorityVO);
    }

    /**
     * 获取用户拥有的菜单及权限
     * @param userUnionId 用户统一id
     */
    public UserAuthoritiesResVO getUserAuthorities(String userUnionId) {

        logger.debug("Get menu and authorities for user {}", userUnionId);
        Timers timers = Timers.createAndBegin(true);
        UserAuthoritiesResVO userAuthoritiesResVO = new UserAuthoritiesResVO();
        User user = userRepository.findByUserUnionId(userUnionId);
        if (user == null || !Objects.equals(user.getStatus(), CommonStatus.ENABLE)) {
            return userAuthoritiesResVO;
        }
        Set<GrantedAuthority> grantedAuthorities = newHashSet();
        List<MenuItem> menuItems;
        if (Objects.equals(userUnionId, serviceAuthorityConfig.getDefaultSuperAdminId())) {
            menuItems = menuItemRepository.findByStatusOrderByOrderIndexAsc(CommonStatus.ENABLE);
            for (MenuItem menuItem : menuItems) {
                grantedAuthorities.addAll(convertToGrantedAuthorities(menuItem.getResources()));
            }
        } else {
            List<Role> roles = user.getRoles();
            menuItems = newArrayList();
            for (Role role : roles) {
                menuItems.addAll(role.getMenuItems());
                grantedAuthorities.addAll(convertToGrantedAuthorities(role.getResources()));
            }
        }
        userAuthoritiesResVO.setMenuGroups(buildMenuGroups(menuItems));
        grantedAuthorities.addAll(convertToGrantedAuthorities(menuItems));
        userAuthoritiesResVO.setGrantedAuthorities(grantedAuthorities);
        timers.print("查询用户菜单权限");
        return userAuthoritiesResVO;
    }

    @Transactional
    public Map<String, MenuGroup> updateMenuGroup(ParsedAuthorityVO reqVO) {
        List<MenuGroup> existMenuGroups = menuGroupRepository.findAllByOrderByOrderIndexAsc();
        Map<String, MenuGroup> menuGroupIdToExistMenuGroup = customerIdAbleEntityToIdMap(existMenuGroups);
        Set<MenuGroup> reqMenuGroups = reqVO.getMenuGroups();
        List<MenuGroup> menuGroupsForSave = newArrayList();
        for (MenuGroup menuGroupOfRequest : reqMenuGroups) {

            MenuGroup existMenuGroup = menuGroupIdToExistMenuGroup.get(menuGroupOfRequest.getCustomId());
            MenuGroup parentMenuGroup = menuGroupOfRequest.getParent();
            if (menuGroupOfRequest.getParent() != null) {
                MenuGroup existParentMenuGroup = menuGroupIdToExistMenuGroup.get(menuGroupOfRequest.getParent().getCustomId());
                if (existParentMenuGroup != null) {
                    parentMenuGroup = existParentMenuGroup;
                }
            }
            if (existMenuGroup != null) {
                if (needUpdate(existMenuGroup, menuGroupOfRequest)
                        || !Objects.equals(existMenuGroup.getIcon(), menuGroupOfRequest.getIcon())) {

                    existMenuGroup.setIcon(menuGroupOfRequest.getIcon());
                    copyValueForIDescribableItem(menuGroupOfRequest, existMenuGroup);
                    existMenuGroup.setParent(parentMenuGroup);
                    menuGroupsForSave.add(existMenuGroup);
                }
            } else {
                menuGroupOfRequest.setParent(parentMenuGroup);
                menuGroupsForSave.add(menuGroupOfRequest);
            }
        }
        menuGroupRepository.save(menuGroupsForSave);
        return customerIdAbleEntityToIdMap(menuGroupRepository.findAll());
    }

    @Transactional
    public Map<String, MenuItem> updateMenuItems(Map<String, MenuGroup> menuGroupCustomerIdToMenuGroup, ParsedAuthorityVO reqVO) {
        List<MenuItem> existMenuItems = menuItemRepository.findAllByOrderByOrderIndexAsc();
        Map<String, MenuItem> menuItemIdToExistMenuItem = customerIdAbleEntityToIdMap(existMenuItems);
        Set<MenuItem> reqMenuItems = reqVO.getMenuItems();
        List<MenuItem> menuItemsForSave = newArrayList();
        for (MenuItem menuItemOfReq : reqMenuItems) {
            MenuItem existMenuItem = menuItemIdToExistMenuItem.get(menuItemOfReq.getCustomId());
            if (existMenuItem != null) {
                if (needUpdate(existMenuItem, menuItemOfReq)
                        || !Objects.equals(existMenuItem.getIcon(), menuItemOfReq.getIcon())
                        || !Objects.equals(existMenuItem.getLink(), menuItemOfReq.getLink())
                        || !Objects.equals(existMenuItem.getAuthorities(), menuItemOfReq.getAuthorities())) {
                    existMenuItem.setIcon(menuItemOfReq.getIcon());
                    existMenuItem.setLink(menuItemOfReq.getLink());
                    existMenuItem.setAuthorities(menuItemOfReq.getAuthorities());
                    copyValueForIDescribableItem(menuItemOfReq, existMenuItem);
                    existMenuItem.setMenuGroup(menuGroupCustomerIdToMenuGroup.get(existMenuItem.getMenuGroup().getCustomId()));
                    menuItemsForSave.add(existMenuItem);
                }
            } else {
                menuItemOfReq.setMenuGroup(menuGroupCustomerIdToMenuGroup.get(menuItemOfReq.getMenuGroup().getCustomId()));
                menuItemsForSave.add(menuItemOfReq);
            }
        }
        menuItemRepository.save(menuItemsForSave);
        return customerIdAbleEntityToIdMap(menuItemRepository.findAll());
    }

    @Transactional
    public void updateResources(Map<String, MenuItem> menuItemCustomerIdToMenuItem, ParsedAuthorityVO reqVO) {
        List<Resource> existResources = resourceRepository.findAllByOrderByOrderIndexAsc();
        Map<String, Resource> resourceIdToExistResource = customerIdAbleEntityToIdMap(existResources);
        List<Resource> menuItemsForSave = newArrayList();
        for (Resource resourceOfReq : reqVO.getResources()) {
            Resource resource = resourceIdToExistResource.get(resourceOfReq.getCustomId());
            if (resource != null) {
                if (needUpdate(resource, resourceOfReq)
                        || !Objects.equals(resource.getAuthorities(), resourceOfReq.getAuthorities())) {
                    copyValueForIDescribableItem(resourceOfReq, resource);
                    resource.setAuthorities(resourceOfReq.getAuthorities());
                    resource.setMenuItem(menuItemCustomerIdToMenuItem.get(resourceOfReq.getMenuItem().getCustomId()));
                    menuItemsForSave.add(resource);
                }
            } else {
                resourceOfReq.setMenuItem(menuItemCustomerIdToMenuItem.get(resourceOfReq.getMenuItem().getCustomId()));
                menuItemsForSave.add(resourceOfReq);
            }
        }
        resourceRepository.save(menuItemsForSave);
    }

    private Collection<MenuGroupVO> buildMenuGroups(List<MenuItem> menuItems) {

        Set<MenuGroupVO> finalMenuGroups = newHashSet();
        Map<Long, MenuGroupVO> idToMenuGroupVO = newHashMap();
        if(CollectionUtils.isNotEmpty(menuItems)) {
            List<ApplicationType> applicationTypes = applicationTypeRepository.findAll();
            HashMap<String, String> applicationTypeToApplicationHost = newHashMap();
            for (ApplicationType applicationType : applicationTypes) {
                applicationTypeToApplicationHost.put(applicationType.getApplicationType(), applicationType.getApplicationTypeUrl());
            }
            for (MenuItem item : menuItems) {
                MenuGroup currentMenuGroup = item.getMenuGroup();
                MenuGroupVO childOfCurrentMenuGroup = null;
                while (currentMenuGroup != null) {
                    MenuGroupVO menuGroupVO = idToMenuGroupVO.get(currentMenuGroup.getId());
                    if (menuGroupVO == null) {
                        menuGroupVO = new MenuGroupVO();
                        idToMenuGroupVO.put(currentMenuGroup.getId(), menuGroupVO);
                        copyValueForIDescribableItem(currentMenuGroup, menuGroupVO);
                        menuGroupVO.setIcon(currentMenuGroup.getIcon());
                    }
                    if (childOfCurrentMenuGroup != null) {
                        menuGroupVO.addChild(childOfCurrentMenuGroup);
                    }
                    if (currentMenuGroup.getParent() != null) {
                        childOfCurrentMenuGroup = menuGroupVO;
                        currentMenuGroup = currentMenuGroup.getParent();
                    } else {
                        finalMenuGroups.add(menuGroupVO);
                        break;
                    }
                }
                MenuGroupVO menuGroupVO = idToMenuGroupVO.get(item.getMenuGroup().getId());
                MenuItemVO menuItemVO = new MenuItemVO();
                copyValueForIDescribableItem(item, menuItemVO);
                menuItemVO.setIcon(item.getIcon());
                String strApplicationType = item.getApplicationType();
                String applicationHost = applicationTypeToApplicationHost.get(strApplicationType);
                if(isNotBlank(applicationHost)){
                    String url = applicationHost + item.getLink();
                    menuItemVO.setUrl(url);
                }else{
                    menuItemVO.setUrl(item.getLink());
                }
                menuGroupVO.addMenuItem(menuItemVO);
            }
        }
        List<MenuGroupVO> sortedFinalMenuGroups = newArrayList(finalMenuGroups);
        sortedFinalMenuGroups.sort(Comparator.comparing(AbstractDescribableItem::getOrderIndex));
        return finalMenuGroups;
    }

    private ParsedAuthorityVO convertToAuthorityEntities(String applicationType, Collection<MenuGroupVO> menuGroupVos, MenuGroup parent) {
        ParsedAuthorityVO parsedAuthorityVO = new ParsedAuthorityVO();
        for (MenuGroupVO menuGroupVo : menuGroupVos) {

            MenuGroup menuGroup = new MenuGroup();
            menuGroup.setId(idService.getNextId());
            menuGroup.setApplicationType(applicationType);
            menuGroup.setIcon(menuGroupVo.getIcon());
            copyValueForIDescribableItem(menuGroupVo, menuGroup);
            menuGroup.setParent(parent);
            parsedAuthorityVO.addMenuGroup(menuGroup);

            if (CollectionUtils.isNotEmpty(menuGroupVo.getMenuItems())) {
                for (MenuItemVO menuItemVO : menuGroupVo.getMenuItems()) {
                    MenuItem menuItem = new MenuItem();
                    menuItem.setId(idService.getNextId());
                    menuItem.setApplicationType(applicationType);
                    menuItem.setIcon(menuItemVO.getIcon());
                    menuItem.setLink(menuItemVO.getUrl());
                    if(menuItemVO.getGrantedAuthority() != null){
                        menuItem.setAuthorities(menuItemVO.getGrantedAuthority().getAuthority());
                    }
                    copyValueForIDescribableItem(menuItemVO, menuItem);
                    menuItem.setMenuGroup(new MenuGroup(menuGroupVo.getCustomId()));
                    parsedAuthorityVO.addMenuItem(menuItem);

                    if (CollectionUtils.isNotEmpty(menuItemVO.getResources())) {
                        for (ResourceVO resourceVO : menuItemVO.getResources()) {
                            Resource resource = new Resource();
                            resource.setId(idService.getNextId());
                            if(resourceVO.getGrantedAuthority() != null) {
                                resource.setAuthorities(resourceVO.getGrantedAuthority().getAuthority());
                            }
                            copyValueForIDescribableItem(resourceVO, resource);
                            resource.setMenuItem(new MenuItem(menuItemVO.getCustomId()));
                            parsedAuthorityVO.addResource(resource);
                        }
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(menuGroupVo.getChildren())) {
                parsedAuthorityVO.add(convertToAuthorityEntities(applicationType, menuGroupVo.getChildren(), menuGroup));
            }
        }
        return parsedAuthorityVO;
    }

    private Set<GrantedAuthority> convertToGrantedAuthorities(List<? extends IAuthoritiesAble> authoritiesAbles) {

        Set<GrantedAuthority> grantedAuthorities = newHashSet();
        if (CollectionUtils.isNotEmpty(authoritiesAbles)) {
            for (IAuthoritiesAble authoritiesAble : authoritiesAbles) {
                String authorities = authoritiesAble.getAuthorities();
                if (isNotBlank(authorities)) {
                    String[] stringAuthArray = authorities.split("[^\\w_]+");
                    for (String stringAuth : stringAuthArray) {
                        grantedAuthorities.add(new GrantedAuthority(stringAuth));
                    }
                }
            }
        }
        return grantedAuthorities;
    }

    private Boolean needUpdate(IDescribableItem source, IDescribableItem target) {

        return !Objects.equals(source.getStatus(), target.getStatus())
                || !Objects.equals(source.getOrderIndex(), target.getOrderIndex())
                || !Objects.equals(source.getName(), target.getName())
                || !Objects.equals(source.getDescription(), target.getDescription());
    }

    private void copyValueForIDescribableItem(IDescribableItem source, IDescribableItem target) {

        target.setCustomId(source.getCustomId());
        target.setName(source.getName());
        target.setDescription(source.getDescription());
        target.setOrderIndex(source.getOrderIndex());
        target.setStatus(source.getStatus());
    }

    private <T extends ICustomIdAble> Map<String, T> customerIdAbleEntityToIdMap(List<T> savedResources) {
        Map<String, T> entityCustomerIdToEntity = newHashMap();
        for (T resource : savedResources) {
            entityCustomerIdToEntity.put(resource.getCustomId(), resource);
        }
        return entityCustomerIdToEntity;
    }

}
